<?php

namespace ADIOS\Prototype;

class Builder {

  const REGENERATE_ALLOWED_TAG = "# prototypeBuilderRegenerateAllowed";

  protected string $inputPath = '';
  protected string $inputFile = '';
  protected string $outputFolder = '';
  protected string $sessionSalt = '';
  protected string $logFile = '';
  protected string $adminPassword = '';

  protected array $prototype = [];
  protected ?\Twig\Loader\ArrayLoader $twigArrayLoader = NULL;
  protected ?\Twig\Loader\FilesystemLoader $twigFilesystemLoader = NULL;
  protected ?\Twig\Environment $twig = NULL;
  protected $logHandle = NULL;

  public function __construct(string $inputFile, string $outputFolder, string $sessionSalt, string $logFile) {
    $this->inputFile = $inputFile;
    $this->outputFolder = $outputFolder;
    $this->sessionSalt = $sessionSalt;
    $this->logFile = $logFile;

    if (empty($this->outputFolder)) {
      throw new \Exception("No output folder for the prototype project provided.");
    }

    if (!is_dir($this->outputFolder)) {
      throw new \Exception("Output folder does not exist.");
    }

    if (!is_file($this->inputFile)) {
      $this->inputPath = $this->inputFile;
      $this->inputFile = $this->inputFile . 'index.json';
    } else {
      $this->inputPath = pathinfo($this->inputFile, PATHINFO_DIRNAME) . '/';
    }

    if (!is_file($this->inputFile)) throw new \Exception('Input file not found: ' . $this->inputFile);

    $this->prototype = $this->parsePrototypeFile($this->inputFile);

    $this->logHandle = fopen($this->logFile, "w");

    if (!is_array($this->prototype["ConfigApp"])) throw new \Exception("ConfigApp is missing in prototype definition.");

    $this->prototype["ConfigApp"]["sessionSalt"] = $this->sessionSalt;

    $this->twigArrayLoader = new \Twig\Loader\ArrayLoader([]);
    $this->twigFilesystemLoader = new \Twig\Loader\FilesystemLoader(__DIR__.'/Templates');
    $twigLoader = new \Twig\Loader\ChainLoader([$this->twigFilesystemLoader, $this->twigArrayLoader]);
    $this->twig = new \Twig\Environment($twigLoader, [
      'cache' => FALSE,
      'debug' => TRUE,
    ]);
    $this->twig->addFunction(new \Twig\TwigFunction(
      'getVariableType',
      function ($var) {
        if (is_numeric($var)) $type = "numeric";
        else if (is_bool($var)) $type = "bool";
        else $type = "string";

        return $type;
      }
    ));
    $this->twig->addFunction(new \Twig\TwigFunction(
      'varExport',
      function ($var, $indent = "") {
        if (is_string($var)) {
          $var = $var;
        } else {
          $var = $indent.str_replace("\n", "\n{$indent}", var_export($var, TRUE));
        }

        $var = preg_replace_callback(
          '/\'{php (.*?) php}\'/',
          function ($m) {
            return str_replace('\\\'', '\'', $m[1]);
          },
          $var
        );

        return $var;
      }
    ));

    $this->checkPrototype();
  }

  public function __destruct() {
    fclose($this->logHandle);
  }

  public function log($msg) {
    fwrite($this->logHandle, $msg."\n");
    echo $msg."\n";
  }

  public function checkPrototype() {
    if (!is_array($this->prototype)) throw new \Exception("Prototype definition must be an array.");
  }

  public function setConfigEnv($configEnv) {
    $this->prototype['ConfigEnv'] = $configEnv;
  }

  public function setAdminPassword($adminPassword) {
    $this->adminPassword = $adminPassword;
  }

  public function createFolder($folder) {
    $this->log("Creating folder {$folder}.");
    @mkdir($this->outputFolder."/".$folder);
  }

  public function removeFolder($dir) { 
   if (is_dir($dir)) { 
      $objects = scandir($this->outputFolder.DIRECTORY_SEPARATOR.$dir);
      foreach ($objects as $object) {
        if (in_array($object, [".", ".."])) continue;
        if (
          is_dir($dir.DIRECTORY_SEPARATOR.$object)
          && !is_link($dir.DIRECTORY_SEPARATOR.$object)
        ) {
          $this->removeFolder($dir.DIRECTORY_SEPARATOR.$object);
        } else {
          unlink($dir.DIRECTORY_SEPARATOR.$object);
        }
      }
      rmdir($this->outputFolder.DIRECTORY_SEPARATOR.$dir);
    }
  }

  public function getBuilderInfo() {
    return [
      'php' => 
        "/**\r\n"
        ." *\r\n"
        ." * This file was generated by the ADIOS prototype builder.\r\n"
        ." *\r\n"
        ." * If you do not want to re-generate this file again, delete the following tag: \r\n"
        ." *\r\n"
        ." * " . self::REGENERATE_ALLOWED_TAG . "\r\n"
        ." *\r\n"
        ." */"
      ,
      'html' => 
        "<!--\r\n"
        ."  \r\n"
        ."  This file was generated by the ADIOS prototype builder.\r\n"
        ."\r\n"
        ."  If you do not want to re-generate this file again, delete the following tag: \r\n"
        ."\r\n"
        ."  " . self::REGENERATE_ALLOWED_TAG . "\r\n"
        ."  \r\n"
        ."-->"
      ,
    ];
  }

  public function fileCanBeRerendered(string $file): bool {
    if (file_exists($file)) {
      $canRender = (strpos(file_get_contents($file), self::REGENERATE_ALLOWED_TAG) !== FALSE);
    } else {
      $canRender = TRUE;
    }
    return $canRender;
  }

  public function renderPhpFile($fileName, $template, $twigParams = NULL) {
    if (empty($fileName)) {
      $this->log("Not rendering PHP file from {$template} - filename not provided.");
      return;
    }

    $twigParams['appNamespace'] = $this->prototype['ConfigApp']['appNamespace'] ?? 'App';

    $outputFile = $this->outputFolder."/".$fileName;
    $canRender = FALSE;

    $this->log("Rendering PHP file {$outputFile} from {$template}.");

    if ($this->fileCanBeRerendered($outputFile)) {
      $params = $twigParams ?? $this->prototype;
      $params['builderInfo'] = $this->getBuilderInfo();

      file_put_contents(
        $outputFile,
        $this->twig->render($template, $params)
      );
    }
  }

  public function renderTwigFile($fileName, $template) {

    $outputFile = $this->outputFolder."/".$fileName;
    $canRender = FALSE;

    $this->log("Rendering file {$outputFile} from {$template}.");

    if ($this->fileCanBeRerendered($outputFile)) {
      $builderInfo = $this->getBuilderInfo();

      file_put_contents(
        $outputFile,
        str_replace(
          '{# builderInfo #}',
          '{# ' . $builderInfo['html'] . ' #}',
          file_get_contents(__DIR__ . '/Templates/' . $template)
        )
      );
    }
  }

  public function copyFile($srcFile, $destFile) {
    $this->log("Copying file {$srcFile} to {$destFile}.");

    if (!file_exists(__DIR__ . "/Templates/" . $srcFile)) {
      throw new \Exception("File " . __DIR__ . "/Templates/{$srcFile} does not exist.");
    } else if (!file_exists($this->outputFolder . "/" . $destFile)) {
      copy(
        __DIR__ . "/Templates/" . $srcFile,
        $this->outputFolder . "/" . $destFile
      );
    }

  }

  public function parsePrototypeFile(string $file): array {

    if (!is_file($file)) {
      throw new \Exception("Parse file: File not found ({$file})");
    }

    $format = strtolower(pathinfo($file, PATHINFO_EXTENSION));

    switch ($format) {
      case 'json':
        $prototype = json_decode(file_get_contents($file), TRUE);
      break;
      case 'yml':
        $ymlContent = file_get_contents($file);
        // if (is_array($this->prototype['_replacements'])) {
        //   foreach ($this->prototype['_replacements'] as $from => $to) {
        //     $ymlContent = str_replace($from, $to, $ymlContent);
        //   }
        // }
        $prototype = \Symfony\Component\Yaml\Yaml::parse($ymlContent);
      break;
      default:
        $prototype = [];
      break;
    }

    if (!is_array($prototype)) $prototype = [];

    return $prototype;
  }

  public function buildPrototype() {
    $appNamespace = $this->prototype['ConfigApp']['appNamespace'] ?? 'App';

  // delete folders if they exist
    $this->removeFolder('src/Widgets');
    $this->removeFolder('log');
    $this->removeFolder('tmp');
    $this->removeFolder('upload');

    // create folder structure
    $this->createFolder('src');
    $this->createFolder('src/Assets');
    $this->createFolder('src/Assets/images');
    $this->createFolder('src/Core');
    $this->createFolder('src/Models');
    $this->createFolder('src/Controllers');
    $this->createFolder('src/Views');
    $this->createFolder('src/Widgets');
    $this->createFolder('log');
    $this->createFolder('tmp');
    $this->createFolder('upload');

    // render files
    $this->copyFile('src/Assets/images/favicon.png', 'src/Assets/images/favicon.png');
    $this->copyFile('src/Assets/images/logo.png', 'src/Assets/images/logo.png');
    $this->copyFile('src/Assets/images/login-screen.jpg', 'src/Assets/images/login-screen.jpg');
    $this->copyFile('.htaccess', '.htaccess');
    $this->copyFile('.htaccess-subfolder', 'log/.htaccess');
    $this->copyFile('.htaccess-subfolder', 'tmp/.htaccess');
    $this->copyFile('.htaccess-subfolder', 'upload/.htaccess');

    $this->renderPhpFile('index.php', 'index.php.twig');
    $this->renderPhpFile('web.php', 'web.php.twig');
    $this->renderPhpFile('ConfigEnv.php', 'ConfigEnv.php.twig');
    $this->renderTwigFile('src/Views/Desktop.twig', 'src/Views/Desktop.twig');
    $this->renderTwigFile('src/Views/Login.twig', 'src/Views/Login.twig');

    $this->renderPhpFile(
      'install.php',
      'install.php.twig',
      [
        'adminPassword' => $this->adminPassword ?? 'admin.'.rand(1000, 9999),
      ]
    );

    $routing = [];
    $permissions = [];

    $configWidgetsEnabled = [];
    foreach ($this->prototype['Widgets'] as $widgetName => $widgetConfig) {
      if (strpos($widgetName, '/') !== FALSE) {
        $tmpCfg = &$configWidgetsEnabled;
        $tmpDirs = explode('/', $widgetName);

        foreach ($tmpDirs as $tmpLevel => $tmpDir) {
          if ($tmpLevel == count($tmpDirs) - 1) {
            $tmpCfg[$tmpDir]['enabled'] = TRUE;
          } else {
            if (!isset($tmpCfg[$tmpDir])) {
              $tmpCfg[$tmpDir] = NULL;
            }
            $tmpCfg = &$tmpCfg[$tmpDir];
          }
        }

      } else {
        $configWidgetsEnabled[$widgetName]['enabled'] = TRUE;
      }
    }

    $this->renderPhpFile("src/ConfigApp.php", "src/ConfigApp.php.twig", array_merge(
      $this->prototype,
      [
        'configWidgetsEnabled' => $configWidgetsEnabled,
      ]
    ));

    // TODO: spravit @import univerzalny, nie iba pre importovanie do Widgets.

    // render widgets
    foreach ($this->prototype['Widgets'] as $widgetName => $widgetConfig) {
      $this->log('Building widget ' . $widgetName);

      $widgetNamespace = $appNamespace . '\Widgets';
      $widgetClassName = '';

      if (strpos($widgetName, '/') !== FALSE) {
        $widgetRootDir = 'src/Widgets';

        $tmpDirs = explode('/', $widgetName);
        
        $widgetClassName = end($tmpDirs);

        foreach ($tmpDirs as $level => $tmpDir) {
          $widgetRootDir .= '/' . $tmpDir;

          if ($level != count($tmpDirs) - 1) {
            $widgetNamespace .= '\\' . $tmpDir;
          }

          $this->createFolder($widgetRootDir);
        }
      } else {
        $widgetRootDir = 'src/Widgets/' . $widgetName;
        $widgetClassName = $widgetName;
      }

      if (is_string($widgetConfig) && strpos($widgetConfig, "@import") !== false) {
        $filePath = trim(str_replace("@import", "", $widgetConfig));
        $this->log('Importing ' . $filePath);
        $widgetConfig = $this->parsePrototypeFile($this->inputPath . $filePath);
      }

      $this->createFolder("src/Widgets/{$widgetName}");
      $this->renderPhpFile(
        $widgetRootDir . '/Main.php',
        'src/Widgets/WidgetMain.php.twig',
        array_merge(
          $this->prototype,
          [
            'thisWidget' => [
              'name' => $widgetName,
              'namespace' => $widgetNamespace,
              'class' => $widgetClassName,
              'config' => $widgetConfig
            ]
          ]
        )
      );

      if (is_array($widgetConfig['routing'] ?? NULL)) {
        $widgetRouting = [];
        foreach ($widgetConfig['routing'] as $key => $value) {
          $key = '/' . str_replace('/', '\/', $key) . '/';
          $widgetRouting[$key] = $value;
        }
        $routing = array_merge($routing, $widgetRouting);
      }

      if (is_array($widgetConfig['permissions'] ?? NULL)) {
        $permissions = array_merge_recursive($permissions, $widgetConfig['permissions']);
      }

      if (is_array($widgetConfig['models'] ?? NULL)) {
        $this->createFolder($widgetRootDir . '/Models');

        //$modelsSqlNames = [];
        //foreach ($widgetConfig['models'] as $modelName => $modelConfig) {
        //  $modelsSqlNames[
        //    str_replace('\\', '/', $widgetNamespace . '/' . $widgetClassName . '/Models/' . $modelName)
        //  ] = $modelConfig['sqlName'];
        //}

        foreach ($widgetConfig['models'] as $modelName => $modelConfig) {
          $modelPrototypeBuilderConfig = $modelConfig['_prototypeBuilder'] ?? [];
          unset($modelConfig['_prototypeBuilder']);

          // Eloquent relatioship functions
          $modelConfig['eloquentRelationships'] = [];
          foreach ($modelConfig['columns'] as $columnName => $column) {
            if ($column['type'] == 'lookup') {
              $modelConfig['eloquentRelationships'][] = [
                //'name' => $modelsSqlNames[$column['model']],
                'column' => $columnName,
                'type' => 'BelongsTo',
                'model' => str_replace('/', '\\', $column['model'])
              ];
            }
          }

          $tmpModelParams = array_merge(
            $this->prototype,
            [
              'thisWidget' => [
                'name' => $widgetName,
                'namespace' => $widgetNamespace,
                'class' => $widgetClassName,
                'config' => $widgetConfig
              ],
              'thisModel' => [
                'namespace' => $widgetNamespace . '\\' . $widgetClassName . '\Models',
                'class' => $modelName,
                'config' => $modelConfig,
              ],
              '_prototypeBuilder' => $modelPrototypeBuilderConfig,
            ]
          );

          $this->renderPhpFile(
            $widgetRootDir . '/Models/' . $modelName . '.php',
            'src/Widgets/Model.php.twig',
            $tmpModelParams
          );

          if ($modelPrototypeBuilderConfig['hasCallbacks'] ?? FALSE) {
            $this->createFolder($widgetRootDir . '/Models/Callbacks');
            $this->renderPhpFile(
              $widgetRootDir . '/Models/Callbacks/' . $modelName . '.php',
              'src/Widgets/ModelCallbacks.php.twig',
              $tmpModelParams
            );
          }
        }
      }


      // render controllers
      if (is_array($widgetConfig['controllers'] ?? NULL)) {
        $this->createFolder($widgetRootDir . '/Controllers');
        $this->createFolder($widgetRootDir . '/Views');

        foreach ($widgetConfig['controllers'] as $controllerName => $controllerConfig) {
          $controllerName = substr($controllerName, strlen($widgetName) + 1);
          $viewTwigTemplate = '';

          if (isset($controllerConfig['phpTemplate'])) {
            $controllerPhpFileTemplate = "src/Widgets/Controllers/{$controllerConfig['phpTemplate']}.php.twig";
          } else {
            $controllerPhpFileTemplate = '';
          }

          $tmpControllerConfig = $controllerConfig;
          unset($tmpControllerConfig['template']);
          unset($tmpControllerConfig['phpTemplate']);

          $controllerNamespace = $widgetNamespace . '\\' . $widgetClassName . '\Controllers';
          $controllerClassName = str_replace('/', '\\', $controllerName);

          // $traitNamespace = $widgetNamespace . '\\' . $widgetClassName . '\Traits\Controllers';

          if (strpos($controllerName, '/') !== FALSE) {
            $controllerRootDir = $widgetRootDir . '/Controllers';
            // $viewRootDir = $widgetRootDir . '/Views';

            $tmpDirs = explode('/', $controllerName);
            
            $controllerClassName = end($tmpDirs);

            foreach ($tmpDirs as $level => $tmpDir) {
              $controllerRootDir .= '/' . $tmpDir;
              // $viewRootDir .= '/' . $tmpDir;

              if ($level != count($tmpDirs) - 1) {
                $controllerNamespace .= '\\' . $tmpDir;

                // $traitNamespace .= '\\' . $tmpDir;

                $this->createFolder($controllerRootDir);
                // $this->createFolder($viewRootDir);
              }

            }
          } else {
            $controllerClassName = $controllerName;
          }

          $tmpControllerParams = array_merge(
            $this->prototype,
            [
              'thisWidget' => [
                'name' => $widgetName,
                'namespace' => $widgetNamespace,
                'class' => $widgetClassName,
                'config' => $widgetConfig
              ],
              'thisController' => [
                'name' => $controllerName,
                'namespace' => $controllerNamespace,
                'class' => $controllerClassName,
                'config' => $tmpControllerConfig
              ]
            ]
          );

          $this->renderPhpFile(
            $widgetRootDir . '/Controllers/' . $controllerName . '.php',
            $controllerPhpFileTemplate,
            $tmpControllerParams
          );

        }
      }

      if (!empty($widgetConfig['javascript'])) {
        file_put_contents($this->outputFolder . '/' . $widgetRootDir . '/main.js', $widgetConfig['javascript']);
      }
    }

    // Create default views
    foreach ($routing as $route) {
      if (!isset($route['view'])) continue;
      
      if (substr($route['view'], 0, 6) == 'ADIOS/') {
        $viewTwigTemplate = ''; // nevygenerujem ziadne view, pretoze pouzivam ADIOSove
      } else {
        $viewTwigTemplate = 'src/Widgets/Views/DefaultEmpty.twig';
      }

      if (
        isset($viewTwigTemplate)
        && !empty($viewTwigTemplate)
        && !is_file($widgetRootDir . '/Views/' . $route['view'] . '.twig')
      ) {
        $view = substr($route['view'], strlen($appNamespace . '/'));
        $viewRootDir = $this->outputFolder . '/src';
        $tmpDirs = explode('/', $route['view']);

        foreach (explode('/', $view) as $level => $tmpDir) {
          $viewRootDir .= '/' . $tmpDir;
          if ($level != count($tmpDirs) - 1) $this->createFolder($viewRootDir);
        }

        $this->renderTwigFile(
          'src/' . $view . '.twig',
          $viewTwigTemplate
        );
      }

    }

    $this->renderPhpFile('src/App.php', 'src/App.php.twig');
    $this->renderPhpFile('src/Core/Controller.php', 'src/Core/Controller.php.twig');
    $this->renderPhpFile('src/Core/Permissions.php', 'src/Core/Permissions.php.twig', ['permissions' => $permissions]);
    $this->renderPhpFile('src/Core/Router.php', 'src/Core/Router.php.twig', ['routing' => $routing]);
    $this->renderPhpFile('src/Models/User.php', 'src/Models/User.php.twig', []);
    $this->renderPhpFile('src/Models/UserRole.php', 'src/Models/UserRole.php.twig', []);
    $this->renderPhpFile('src/Models/UserHasRole.php', 'src/Models/UserHasRole.php.twig', []);
    $this->renderPhpFile('src/Controllers/Desktop.php', 'src/Controllers/Desktop.php.twig');
  }

}
